#include "cli_handler.h"
#include <argz.h>
#include <search.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include <netdb.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#include "pwzd.h"
#include "multicast.h"
#include "hello/clientinfo.h"
#include "hello/hello.h"
#include "hashtbl.h"

#define MAXBUFLEN 1500
#define for_each(i, node, hash)  for (i = 0; i < (hash)->size; ++i) for (node = ((hash)->nodes)[i]; node; node = node->next)

void handle_cmd(char * cmd, size_t cmd_len)
{
	char * data;
	if (cmd_len < 1) {
        printf("Rozkaz z CLI za krótki (<   1). Ignoruję\n");
		return;
	}

	data     = cmd + 1;

	switch(cmd[0]) {
	case 'd': handle_delete(data); break;
	case 'a': handle_add(data);    break;
	case 'i': handle_info(data);   break;
	case 'q': handle_quit();       break;
    case 'l': handle_list();       break;
    default:  return;
	}
}

int client_info_cmp(const void * l, const void * r) {
    char * left  = (*((struct client_info **)l))->name;
    char * right = (*((struct client_info **)r))->name;
    return strcmp(left, right);
}

int string_cmp(const void * l, const void * r) {
    const char * left  = *((const char **)l);
    const char * right = *((const char **)r);
    if (!left) return 1;
    if (!right) return -1;

    return strcmp(left, right);
}

void handle_list()
{
    size_t client_count = 0;
    hash_size i, j;
    struct hashnode_s *node, *n;

    for_each(i, node, clients) {
        client_count += 1;
    }

    j = 0;
    struct client_info * tab[client_count];
    for_each(i, node, clients) {
        tab[j] = ((struct client_info *)node->data);
        j++;
    }

    qsort(tab, client_count, sizeof(struct client_info*), client_info_cmp);


    char * argz = NULL;
    size_t argz_len = 0;

    for(i = 0; i < client_count; ++i) {
        struct client_info * client = tab[i];
        size_t count = client->interests->count;
        char * interests[count];
        size_t k = 0;
        for_each(j, node, client->interests) {
            interests[k] = node->key;
            k++;
        }

        qsort(interests, count, sizeof (char *), string_cmp);

        argz_add(&argz, &argz_len, client->name);
        for(k = 0; k < count; ++k) {
            char * tabbed_interest = calloc(1, strlen(interests[k]) + 1 + 3);
            strncpy(tabbed_interest, "   ", 3);
            strcpy(tabbed_interest + 3, interests[k]);
            argz_add(&argz, &argz_len, tabbed_interest);
        }
    }
    argz_stringify(argz, argz_len, '\n');

    write(client_sock_fd, argz, argz_len);
}

// protokół dodania zainteresowania xxxxx:
// 1) pisz xxxxx
// 2) odpowiedź: lista zainteresowań w formacie argz
void handle_add(char * interest)
{
    size_t argz_len;
    char * argz;

    hashtbl_res_insert(my_interests, interest, 0);

    argz_len = to_argz(&argz, my_interests);
    write(client_sock_fd, argz, argz_len);
	send_hello();
}

void handle_delete(char * interest)
{
    size_t argz_len;
    char * argz;

    //struct hashnode_s * node;
    //if ((node = hashtbl_get(my_interests, interest))) {
        hashtbl_res_remove(my_interests, interest);
    //}

    argz_len = to_argz(&argz, my_interests);
    write(client_sock_fd, argz, argz_len);

    send_hello();
}

void handle_info(char * interest)
{
    HASHTBL *hash;
    struct hashnode_s *node;
	char * argz           = NULL;
	size_t argz_len       = 0;
    size_t i;
    hash = (HASHTBL*)hashtbl_get(interestToClient, interest);

    if (hash)
    for( i = 0; i < hash->size; ++i ) {
        for ( node = (hash->nodes)[i];
              node; node = node->next ) {
            argz_add(&argz, &argz_len, node->key);
        }
    }
	
    if (argz_len > 0 && argz_count(argz, argz_len) > 0) {
		write(client_sock_fd, argz, argz_len);		
	} else {
		write(client_sock_fd, "\0", 1);
	}
}

void handle_quit()
{ 
	char * msg = "quitting\0";
    hashtbl_destroy(my_interests);
    my_interests = NULL;
    send_hello();
	write(client_sock_fd, msg, strlen(msg) + 1);
	exit(EXIT_SUCCESS);
}

int setup_unixfd()
{
    int sockfd;
    if((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
    {
       perror("socket");
       exit(1);
    }

    struct sockaddr_un server_address;
    server_address.sun_family = AF_UNIX;
    strcpy(server_address.sun_path, "/tmp/videlsock");
    unlink(server_address.sun_path);



    if (bind(sockfd,(struct sockaddr*)&server_address,
             sizeof(struct sockaddr_un)) ) {
       perror("bind");
       exit(1);
    }

    if (listen(sockfd, 5) == -1) {
       perror("listen");
       exit(1);
    }
    return sockfd;
}

void read_unixfd(int unixfd)
{
    struct sockaddr_un client;
    socklen_t client_len;
    client_sock_fd =
    accept(unixfd, (struct sockaddr*)&client, &client_len);

    if (client_sock_fd == -1) {
        perror("accept");
        exit(1);
    }

    char buf[MAXBUFLEN];
    int buflen = recv(client_sock_fd, &buf, MAXBUFLEN, 0);

    handle_cmd(buf, buflen);

    close(client_sock_fd);
}

